<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<meta name="keywords" content="access control design" />
	<meta name="description" content="access control design" />
	<!-- 网页标签标题 -->
	<title>access control design</title>
	<link rel="shortcut icon" href="https://img.alicdn.com/tfs/TB1hgJpHAPoK1RjSZKbXXX1IXXa-64-64.png"/>
	<link rel="stylesheet" href="/build/blogDetail.css" />
</head>
<body>
	<div id="root"><div class="blog-detail-page" data-reactroot=""><header class="header-container header-container-normal"><div class="header-body"><a href="/en-us/index.html"><img class="logo" src="/img/nacos_colorful.png"/></a><div class="search search-normal"><span class="icon-search"></span></div><span class="language-switch language-switch-normal">中</span><div class="header-menu"><img class="header-menu-toggle" src="/img/menu_gray.png"/><ul><li class="menu-item menu-item-normal"><a href="/en-us/index.html">HOME</a></li><li class="menu-item menu-item-normal"><a href="/en-us/docs/quick-start.html">DOCS</a></li><li class="menu-item menu-item-normal menu-item-normal-active"><a href="/en-us/blog">BLOG</a></li><li class="menu-item menu-item-normal"><a href="/en-us/community">COMMUNITY</a></li><li class="menu-item menu-item-normal"><a href="https://cn.aliyun.com/product/aliware/mse?spm=nacos-website.topbar.0.0.0">NACOS IN CLOUD</a><img class="menu-img" src="https://img.alicdn.com/tfs/TB1esl_m.T1gK0jSZFrXXcNCXXa-200-200.png"/></li><li class="menu-item menu-item-normal"><a href="http://console.nacos.io/nacos/index.html">DEMO-CONSOLE</a></li></ul></div></div></header><section class="blog-content markdown-body"><h1>Nacos权限控制设计方案</h1>
<p><a name="oBGKT"></a></p>
<h1>方案背景</h1>
<p>Nacos自开源以来，权限控制一直需求比较强烈，这也反应了用户需求将Nacos部署到生产环境的需求。Nacos 1.2.0版本将会支持服务发现和配置管理的权限控制，保障用户安全上生产。本文主要介绍Nacos权限控制的设计方案，当然这个方案在1.2.0发布前可能会有少许调整，同时也欢迎广大用户对该方案提出自己的建议。</p>
<p><a name="FKbJ4"></a></p>
<h2>什么是权限控制？</h2>
<p>在分布式服务调用时，需要对未知的或者不受信任的请求来源的请求进行识别和拒绝。权限控制一般分为两个阶段：身份识别（Authentication）和权限识别（Authorization）。身份认证主要确定访问者的身份，权限识别则判断这个访问者是否有对应资源的权限。<br />
<br />在Nacos的场景中，配置管理的权限控制指的是设置某个配置能否被某个用户读写，这个比较好理解，没有权限的用户旧无法读取或者写入对应的配置。服务发现的权限控制指的是用户是否有权限进行某个服务的注册或者订阅，这里需要注意的是服务发现的权限控制只能够控制用户是否可以从Nacos获取到服务的地址或者在Nacos上修改服务的地址。但是如果已经获取到了服务的地址，Nacos无法在服务真正调用时进行权限控制，这个时候的权限控制需要由服务框架来完成。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576216016307-2da56934-917f-46ec-b3eb-a221bc91a9e0.png#align=left&amp;display=inline&amp;height=240&amp;name=image.png&amp;originHeight=480&amp;originWidth=1904&amp;size=271408&amp;status=done&amp;style=none&amp;width=952" alt="image.png"></p>
<p><a name="iiXvl"></a></p>
<h2>常见实现方式</h2>
<p><a name="SzK17"></a></p>
<h3>认证（Authentication）</h3>
<ul>
<li>用户名+密码</li>
<li>Cookie（只适用于浏览器）</li>
<li>Session</li>
<li>Token（JWT，Oauth，LDAP，SAML，OpenID）</li>
<li>AK/SK
<a name="3szY3"></a></li>
</ul>
<h3>鉴权（Authorization）</h3>
<ul>
<li>ACL： 规定<strong>资源</strong>可以被哪些<strong>主体</strong>进行哪些操作；</li>
<li>DAC： 规定<strong>资源</strong>可以被哪些<strong>主体</strong>进行哪些操作 同时，<strong>主体</strong>可以将<strong>资源</strong>的权限，授予其他<strong>主体</strong>；</li>
<li>MAC：a. 规定<strong>资源</strong>可以被哪些类别的<strong>主体</strong>进行哪些<strong>操作</strong> b. 规定<strong>主体</strong>可以对哪些等级的<strong>资源</strong>进行哪些<strong>操作</strong> 当一个<strong>操作</strong>，同时满足a与b时，允许<strong>操作</strong>；</li>
<li>RBAC： a. 规定<strong>角色</strong>可以对哪些<strong>资源</strong>进行哪些<strong>操作</strong> b. 规定<strong>主体</strong>拥有哪些<strong>角色</strong>当一个操作，同时满足a与b时，允许<strong>操作</strong>；</li>
<li>ABAC： 规定哪些<strong>属性</strong>的<strong>主体</strong>可以对哪些<strong>属性</strong>的<strong>资源</strong>在哪些<strong>属性</strong>的情况下进行哪些<strong>操作</strong>。
<a name="0FxEV"></a></li>
</ul>
<h2></h2>
<p><a name="0YQ9P"></a></p>
<h2>常见注册中心和配置中心的实现方式</h2>
<p><a name="5yePW"></a></p>
<h3>Zookeeper</h3>
<p>Zookeeper主要使用的是ACL的方式，直接将资源授权给对应的实体。一条授权记录主要由以下部分组成：</p>
<ul>
<li><path>: 设置权限的路径</li>
<li>&lt;acl_type&gt;: ACL鉴权类型，分为world，ip，auth，digest</li>
<li>&lt;acl_content&gt;: ACL鉴权内容，与鉴权类型关联</li>
<li><action>: CREATE，DELETE，READ，WRITE，ADMIN</li>
</ul>
<p>操作示例：</p>
<pre><code class="language-xml">$ setAcl <span class="hljs-tag">&lt;<span class="hljs-name">path</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">acl_type</span>&gt;</span>:<span class="hljs-tag">&lt;<span class="hljs-name">acl_content</span>&gt;</span>:<span class="hljs-tag">&lt;<span class="hljs-name">action</span>&gt;</span>
$ setAcl /xxx/yyy world:anyone:cdrwa
$ setAcl /xxx/yyy ip:1.1.1.1:cdrwa
$ addauth digest root:pa55wdsetAcl /xxx/yyy auth:root:cdrwa
</code></pre>
<p><a name="GI6TE"></a></p>
<h3>Consul</h3>
<p>Consul的鉴权也是偏向于ACL机制，主要分为三个部分：</p>
<ul>
<li>Rule：定义对某个资源的权限</li>
<li>Policy：将一系列Rule组合成一个Policy</li>
<li>Token：为某个Token分配一个或多个Policy，API带上Token进行鉴权</li>
</ul>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576218881317-bb025c9f-f6ad-4df1-9f7f-f116e8d95671.png#align=left&amp;display=inline&amp;height=240&amp;name=image.png&amp;originHeight=273&amp;originWidth=848&amp;size=49225&amp;status=done&amp;style=none&amp;width=746" alt="image.png"></p>
<p><a name="oLlfm"></a></p>
<h3>Eureka</h3>
<p>Eureka使用的鉴权是基于Spring Security实现的，支持用户名和密码的访问控制，一个简单的例子如下：<br /></p>
<pre><code class="language-yaml"><span class="hljs-attr">spring:</span> 
  <span class="hljs-attr">security:</span> 
  <span class="hljs-comment"># 开启认证，Spring Cloud2.0后添加jar会自动集成并开启</span>
  <span class="hljs-comment">#</span>
<span class="hljs-attr">basic.enabled:</span> <span class="hljs-literal">true</span> 
  <span class="hljs-comment"># 用户名密码</span>
  <span class="hljs-attr">user:</span> 
  <span class="hljs-attr">name:</span> <span class="hljs-string">test</span> 
  <span class="hljs-attr">password:</span> <span class="hljs-string">test</span>
</code></pre>
<p><a name="qrC17"></a></p>
<h3>Apollo</h3>
<p>基于RBAC的权限控制，可以在命名空间级别进行资源的授权：<br /><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576218970350-01402621-0a13-4102-a590-20c6cefe4918.png#align=left&amp;display=inline&amp;height=118&amp;name=image.png&amp;originHeight=101&amp;originWidth=640&amp;size=21453&amp;status=done&amp;style=none&amp;width=746" alt="image.png"></p>
<p><a name="zH1U3"></a></p>
<h1>方案详情</h1>
<p>Nacos的权限控制，目标是能够满足用户基本的鉴权需求，同时能够保持扩展性，可以支持去对接用户自带的用户管理系统或者鉴权系统，包括后面和K8S生态以及Service Mesh生态能够无缝的融合。基于这样的考虑，目前Nacos权限控制的设计是自带一个基本的实现，然后可以支持用户扩展。具体的设计如下。
<a name="pd2aV"></a></p>
<h2>模块设计</h2>
<p>整体的模块设计是尽量将鉴权的逻辑抽象出来，不在服务发现模块或者配置管理模块添加相关的逻辑。通过配置文件可以选择当前使用的鉴权系统。Nacos自带的认证系统使用JWT Token，自带的鉴权系统使用的是RBAC。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576219027093-45345003-c583-46ec-a161-01b5f4b3ff47.png#align=left&amp;display=inline&amp;height=450&amp;name=image.png&amp;originHeight=900&amp;originWidth=1744&amp;size=699757&amp;status=done&amp;style=none&amp;width=872" alt="image.png"></p>
<p><a name="vr4PO"></a></p>
<h2>认证算法</h2>
<p>对于用户来说，不管是在控制台还是在客户端，都是上传用户名和密码来获取一个token，然后后续的每一次到Nacos的请求都会带上这个token来表明身份。这个token会有一个失效时间，对于控制台来说，只需要直接提示用户重新登录即可，对于客户端则需要有一个定期到Nacos刷新token的逻辑。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576219050917-51013ce2-49f3-4a86-b5f9-bd07fc88f8e8.png#align=left&amp;display=inline&amp;height=368&amp;name=image.png&amp;originHeight=736&amp;originWidth=1718&amp;size=575605&amp;status=done&amp;style=none&amp;width=859" alt="image.png"></p>
<p><a name="9ncb7"></a></p>
<h2>鉴权算法</h2>
<p>Nacos自带的鉴权系统使用的是RBAC模型，可以在网上查询相关的资料。
<a name="DjMVc"></a></p>
<h3>数据模型</h3>
<p>鉴权的数据模型也是基于标准的RBAC来设计的，分为用户、角色和权限三部分。用户就是由用户名和密码组成的用户信息，角色则是一个逻辑上的用户组，Nacos启动时会自带一个全局管理员的角色，只有这个全局管理员的角色可以进行添加用户、添加角色、添加授权等操作，保证安全性。而权限则是由资源+动作来组成。</p>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576736418792-936a9d1a-5095-47fc-9f87-230abed38384.png#align=left&amp;display=inline&amp;height=451&amp;name=image.png&amp;originHeight=902&amp;originWidth=1834&amp;size=438246&amp;status=done&amp;style=none&amp;width=917" alt="image.png"></p>
<p><a name="gIPMW"></a></p>
<h3>接口设计</h3>
<p>以下接口涉及到登录和鉴权的所有逻辑，这些接口除了登录接口，其他接口都只能由全局管理员来调用。
<a name="yA6U0"></a></p>
<h4>用户管理</h4>
<ul>
<li>创建用户：POST
/nacos/v1/auth/users?username=xx&amp;password=yy</li>
<li>删除用户：DELETE /nacos/v1/auth/users?username=xx&amp;password=yy</li>
<li>更新用户：PUT /nacos/v1/auth/users?username=xx&amp;oldPassword=yy&amp;newPassword=zz</li>
<li>登录：POST
/nacos/v1/auth/users/login?username=xxx&amp;password=yyy</li>
</ul>
<p><a name="eHYVh"></a></p>
<h4>角色管理</h4>
<ul>
<li>创建角色/绑定用户到角色：POST /nacos/v1/auth/roles?role=xx&amp;username=yy</li>
<li>删除某个用户的角色：DELETE /nacos/v1/auth/roles?role=xx&amp;username=yy</li>
<li>获取用户的所有角色：GET /nacos/v1/auth/roles?username=xxx</li>
</ul>
<p><a name="SRZQx"></a></p>
<h4>权限管理</h4>
<ul>
<li>给角色添加权限：POST /nacos/v1/auth/permissions?role=xxx&amp;resource=yyy&amp;action=zzz</li>
<li>从角色删除权限：DELETE /nacos/v1/auth/permissions?role=xxx&amp;resource=yyy&amp;action=zzz</li>
<li>获取某个角色的权限：GET /nacos/v1/auth/permissions?role=xxx</li>
</ul>
<p><a name="Bb2oV"></a></p>
<h2>页面交互</h2>
<p>目前的设计方案可以支持最小到dataId级别的鉴权，但是粒度越细在页面的展示就会越复杂，需要每个资源都去检查是否有权限然后再决定是否展示，对于数据量比较大的情况，会非常影响服务端的性能。不过可以肯定的是一定会支持命名空间级别的读写授权，用户可以在页面配置将某个命名空间的读写权限授权给某一个角色，然后再将这个角色授权给某个用户。至于更细粒度的授权，可能考虑不支持或者在1.2.0之后的版本支持。
<a name="PwF7l"></a></p>
<h3>用户管理</h3>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576225555266-ed32865d-95fb-4719-8d81-b25b55fbe711.png#align=left&amp;display=inline&amp;height=246&amp;name=image.png&amp;originHeight=370&amp;originWidth=1120&amp;size=137189&amp;status=done&amp;style=none&amp;width=746" alt="image.png">
<a name="vEW9w"></a></p>
<h3>角色管理</h3>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576225984713-8134d131-a3b5-4000-8093-d8a793c8b461.png#align=left&amp;display=inline&amp;height=255&amp;name=image.png&amp;originHeight=378&amp;originWidth=1106&amp;size=134468&amp;status=done&amp;style=none&amp;width=746" alt="image.png"></p>
<p><a name="TwHTX"></a></p>
<h3>权限管理</h3>
<p><img src="https://cdn.nlark.com/yuque/0/2019/png/333810/1576226004009-ca20d92d-889d-4926-a0d7-f613013d0f59.png#align=left&amp;display=inline&amp;height=249&amp;name=image.png&amp;originHeight=412&amp;originWidth=1232&amp;size=164158&amp;status=done&amp;style=none&amp;width=746" alt="image.png"></p>
<p><a name="t34hG"></a></p>
<h2>关键逻辑</h2>
<ol>
<li>每个模块继承ResourceParser来实现各自模块的资源名解析器：</li>
</ol>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ResourceParser</span> </span>{
    <span class="hljs-comment">// 输入为请求信息，输出为一个资源名：</span>
    <span class="hljs-function">String <span class="hljs-title">parseResource</span><span class="hljs-params">(Object request)</span></span>;
}
</code></pre>
<ol start="2">
<li>在每个需要鉴权的方法上添加一个注解，来指定这个方法对应的资源名，动作及资源解析器：</li>
</ol>
<pre><code class="language-java"><span class="hljs-meta">@Secured</span>(resource=“service1”,action=“read”, parser=NamingParser<span class="hljs-class">.<span class="hljs-keyword">class</span>)
<span class="hljs-title">public</span> <span class="hljs-title">void</span> <span class="hljs-title">registerInstance</span>() </span>{…}
</code></pre>
<p>这个注解的介绍如下：</p>
<pre><code class="language-java"><span class="hljs-meta">@Retention</span>(RetentionPolicy.RUNTIME)
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> Secured {
    <span class="hljs-comment">// 动作类型，默认为读类型，全部类型有CREAT|DELETE|READ|WRITE|ADMIN</span>
    <span class="hljs-function">ActionTypes <span class="hljs-title">action</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> ActionTypes.READ</span>;
    <span class="hljs-comment">// 资源名，可以显示指定资源名，如不指定，将由资源解析器解析出资源名</span>
    <span class="hljs-function">String <span class="hljs-title">resource</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> ""</span>;
    <span class="hljs-comment">// 资源解析器，解析资源名，优先级比name()低</span>
    Class&lt;? extends ResourceParser&gt; parser() <span class="hljs-keyword">default</span> DefaultResourceParser<span class="hljs-class">.<span class="hljs-keyword">class</span></span>;
}
</code></pre>
<ol start="3">
<li>在一个filter里进行登录和鉴权的逻辑，通过获取注解上的信息来拿到资源和动作，从request里获取到用户信息，然后进行鉴权。</li>
</ol>
<pre><code class="language-java"><span class="hljs-comment">// 判断是否需要鉴权：</span>
<span class="hljs-keyword">if</span> (method.isAnnotationPresent(Secured<span class="hljs-class">.<span class="hljs-keyword">class</span>) &amp;&amp; <span class="hljs-title">authConfigs</span>.<span class="hljs-title">isAuthEnabled</span>()) </span>{
	Secured secured = method.getAnnotation(Secured<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;
	<span class="hljs-comment">// 获取注解里配置的动作类型和资源名：</span>
	String action = secured.action().toString();
	String resource = secured.resource();
	<span class="hljs-comment">// 若资源名为空，进行资源解析：</span>
	<span class="hljs-keyword">if</span> (StringUtils.isBlank(resource)) {
		ResourceParser parser = secured.parser().newInstance();
		resource = parser.parseResource(req);
	}
    <span class="hljs-keyword">if</span> (StringUtils.isBlank(resource)) {
    	<span class="hljs-comment">// 没有找到资源，则直接返回:</span>
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> AccessException(<span class="hljs-string">"resource name invalid!"</span>);
    }
	<span class="hljs-comment">// 先调用login进行认证，再调用auth进行鉴权：</span>
    authManager.auth(<span class="hljs-keyword">new</span> Permission(resource, action), authManager.login(req));
}
</code></pre>
<ol start="3">
<li>鉴权接口抽象如下：</li>
</ol>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">AuthManager</span> </span>{

    <span class="hljs-comment">/**
     * 根据请求进行用户认证，可以由用户进行扩展
     */</span>
    <span class="hljs-function">User <span class="hljs-title">login</span><span class="hljs-params">(Object request)</span> <span class="hljs-keyword">throws</span> AccessException</span>;

    <span class="hljs-comment">/**
     * 根据用户信息和请求的权限，进行授权，也可以由用户进行扩展
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">auth</span><span class="hljs-params">(Permission permission, User user)</span> <span class="hljs-keyword">throws</span> AccessException</span>;
}
</code></pre>
<ol start="4">
<li>Nacos自带的鉴权实现逻辑介绍如下：</li>
</ol>
<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NacosAuthManager</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">AuthManager</span> </span>{

<span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">login</span><span class="hljs-params">(Object request)</span> <span class="hljs-keyword">throws</span> AccessException </span>{
        <span class="hljs-comment">// 从请求中获取用户信息，可以传入token，也可以传入用户名密码。</span>
        <span class="hljs-comment">// 1.传入用户名密码时，验证用户名密码，生成新的token放到User里；</span>
        <span class="hljs-comment">// 2.传入token时，验证token是否有效；</span>
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">auth</span><span class="hljs-params">(Permission permission, User user)</span> <span class="hljs-keyword">throws</span> AccessException </span>{
        <span class="hljs-comment">// 1.从用户信息中拿到角色信息</span>
        <span class="hljs-comment">// 2.从角色信息中获取权限列表</span>
        <span class="hljs-comment">// 3.匹配请求的权限是否在权限列表里</span>
}
</code></pre>
<p><a name="q40cy"></a></p>
<h1>参考资料</h1>
<p>【1】<a href="https://zhuanlan.zhihu.com/p/70548562">https://zhuanlan.zhihu.com/p/70548562</a><br />【2】<a href="https://learn.hashicorp.com/consul/security-networking/production-acls">https://learn.hashicorp.com/consul/security-networking/production-acls</a><br />【3】<a href="https://zookeeper.apache.org/doc/r3.1.2/zookeeperProgrammers.html#sc_ZooKeeperAccessControl">https://zookeeper.apache.org/doc/r3.1.2/zookeeperProgrammers.html#sc_ZooKeeperAccessControl</a></p>
</section><footer class="footer-container"><div class="footer-body"><img src="/img/nacos_gray.png"/><div class="cols-container"><div class="col col-12"><h3>Vision</h3><p>By providing an easy-to-use service infrastructure such as dynamic service discovery, service configuration, service sharing and management and etc., Nacos help users better construct, deliver and manage their own service platform, reuse and composite business service faster and deliver value of business innovation more quickly so as to win market for users in the era of cloud native and in all cloud environments, such as private, mixed, or public clouds.</p></div><div class="col col-6"><dl><dt>Documentation</dt><dd><a href="/en-us/docs/what-is-nacos.html" target="_self">Overview</a></dd><dd><a href="/en-us/docs/quick-start.html" target="_self">Quick start</a></dd><dd><a href="/en-us/docs/contributing.html" target="_self">Developer guide</a></dd></dl></div><div class="col col-6"><dl><dt>Resources</dt><dd><a href="/en-us/community/index.html" target="_self">Community</a></dd><dd><a href="https://cn.aliyun.com/product/aliware/mse?spm=nacos-website.topbar.0.0.0" target="_self">Cloud Service MSE</a></dd><dd><a href="https://www.aliyun.com/product/edas?source_type=nacos_pc_20181219" target="_self">Cloud Service EDAS</a></dd><dd><a href="https://www.aliyun.com/product/ahas?source_type=nacos_pc_20190225" target="_self">Cloud Service AHAS</a></dd></dl></div></div><div class="copyright"><span>@ 2018 The Nacos Authors | An Alibaba Middleware (Aliware) Project</span></div></div></footer></div></div>
	<script src="https://f.alicdn.com/react/15.4.1/react-with-addons.min.js"></script>
	<script src="https://f.alicdn.com/react/15.4.1/react-dom.min.js"></script>
	<script>
		window.rootPath = '';
  </script>
	<script src="/build/blogDetail.js"></script>
</body>
</html>